<?php    
/**
 * Contains the Log_XML_FileToolkit class.
 * 
 * PHP version 5
 * 
 * @category Logging
 * @package  Log
 * @author   Michael Peter 'Vladimyr' Gower <designatevoid@gmail.com>
 * @author   "ilgarm" <ilgarm@azeronline.com>
 * @author   Jon Parise <jon@php.net>
 * @author   Roman Neuhauser <neuhauser@bellavista.cz>
 * @license  http://www.apache.org/licenses/LICENSE-2.0  Apache License 2.0
 * @link     http://pear.php.net/pepr/pepr-proposal-show.php?id=487
 */  
 
/**
 * A 'swiss army knife' class for file functions.
 * 
 * Note: Assumes that both '/' and '\' are directory separators in quoted
 * literal strings, so stay away from 'nix filenames like 'my\file'.
 * 
 * @category Logging
 * @package  Log
 * @author   Michael Peter 'Vladimyr' Gower <designatevoid@gmail.com>
 * @author   "ilgarm" <ilgarm@azeronline.com>
 * @author   Jon Parise <jon@php.net>
 * @author   Roman Neuhauser <neuhauser@bellavista.cz>
 * @license  http://www.apache.org/licenses/LICENSE-2.0  Apache License 2.0
 * @link     http://pear.php.net/pepr/pepr-proposal-show.php?id=487
 * 
 */
class Log_XML_FileToolkit
{
    /**
     * The basic constructor.
     */
    public function __construct()
    {
        if (substr(php_uname(), 0, 7) == 'Windows') {
            $this->currentOS = 'Win';
        } else {
            $this->currentOS = '_Nix';
        }
    }
    
    
    /**
     * Like realpath(), but the file or directory doesn't need to exist.
     * 
     * Can work with absolute paths, '.' and '..'.  
     * Must use DIRECTORY_SEPARATOR for directory seperation.
     * No escaping necessary for backslashes (use '\', not '\\').
     * 
     * Precondition: $path must be a valid filename.
     * 
     * @param string $path the path to resolve to an absolute path.
     * 
     * @return string The real absolute path.
     * @author "ilgarm" <ilgarm@azeronline.com>
     */
    public function getRealpath($path)
    {
        // if php's realpath can handle this, use it
        if (is_file($path) || is_dir($path)) {
            return realpath($path);
        }
        
        $path = $this->unifySlashes($path);
        if (!$this->validFilename($path)) {
            return null;
        }
            
            
        // if relative, convert $path to absolute, besides . or ..
        if ($this->isRelative($path)) {
            $currentDirectory = realpath('.');
            $path             = $currentDirectory . DIRECTORY_SEPARATOR . $path;
        }
        
        // set $windowsDriveLetter to blank for 'nix or 'DRIVE_LETTER:' for Win
        $windowsDriveLetter = '';
        if ($this->checkCurrentOS('Win')) {
            list($windowsDriveLetter, $path) = explode(':', $path, 2);
            $windowsDriveLetter             .= ':';
        }
        
        // From now processing is the same
        $processedParts = array();
        $initialParts   = explode(DIRECTORY_SEPARATOR, $path);
        for ($i = 0; $i < count($initialParts); $i++) {
            if (strlen($initialParts[$i]) == 0 || $initialParts[$i] == '.') {
                continue; // skip '//' or '/./'
            } else if ($initialParts[$i] == '..') {
                if (count($processedParts) > 0) {
                    array_pop($processedParts); // recurse up one
                }
            } else {
                // add to output
                array_push($processedParts, $initialParts[$i]); 
            }
        }
        return $windowsDriveLetter 
            . DIRECTORY_SEPARATOR 
            . implode(DIRECTORY_SEPARATOR, $processedParts);
    }
    
    
    /**
     * Creates the given directory path.  If the parent directories don't
     * already exist, they will be created, too.
     *
     * This implementation is inspired by Python's os.makedirs function.
     * 
     * @param string  $path The full directory path to create.
     * @param integer $mode The permissions mode with which the
     *                       directories will be created.
     *
     * @return True if the full path is successfully created or already
     *          exists.
     *
     * @author Jon Parise <jon@php.net>
     * @author Roman Neuhauser <neuhauser@bellavista.cz>
     */ 
    public function mkpath($path, $mode = 0700)
    {
        
        // Separate the last pathname component from the rest of the path.
        $head = dirname($path);
        $tail = basename($path);

        // Make sure we've split the path into two complete components.
        if (empty($tail)) {
            $head = dirname($path);
            $tail = basename($path);
        }

        // Recurse up the path if our current segment does not exist.
        if (!empty($head) && !empty($tail) && !is_dir($head)) {
            $this->mkpath($head, $mode);
        }

        // Create this segment of the path.
        $ret = @mkdir($head, $mode);
        
        return $ret;
    }


    /**
     * This will remove the directory at $path.
     *
     * @param string $path The directory to remove.
     * 
     * @return void
     */
    public function removeDir($path)
    {
        // Add trailing slash to $path if one is not there
        if (substr($path, -1, 1) != '/') {
            $path .= '/';
        }

        $normal_files = glob($path . '*');
        $hidden_files = glob($path . '\.?*');
        $all_files    = array_merge($normal_files, $hidden_files);

        foreach ($all_files as $file) {
            // Skip pseudo links to current and parent dirs (./ and ../).
            if (preg_match('/(\.|\.\.)$/', $file)) {
                continue;
            } else if (is_file($file) === true) {
                // Remove each file in this Directory
                unlink($file);
            } else if (is_dir($file) === true) {
                // If this Directory contains a Subdirectory,
                // then run this Function on it
                $this->removeDir($file);
            }
        }
            // Remove Directory once Files have been removed (If Exists)
        if (is_dir($path) === true) {
            rmdir($path);
        }
    }
    
    
    /**
     * Checks if a given string is a valid filename.
     * 
     * @param string $filename the filename to validate
     * 
     * @return boolean true if valid, false if not
     * 
     */
    public function validFilename($filename)
    {
        if ($this->checkCurrentOS('Win')) {
            // if has invalid filename, return false
            $regexp = '/PRN|CON|AUX|CLOCK\$|NUL|COM\d|LPT\d/i';
            if (preg_match($regexp, $filename)) {
                return false;
            }
            
            $parts = explode("\\", $filename);
            
            // if a drive letter, approve (don't check it later)
            if (preg_match('/[A-Za-z]:/', $parts[0])) {
                $parts[0] = null;
            }
            
            // checks for invalid printable charactors or control charactors
            foreach ($parts as $part) {
                if (preg_match('|[\^\|\?\*<\":>/\a\b\e\f\n\r\t]|', $part)) {
                    return false;
                }
            }
            return true;            
        } else { // unix always passes
            return true;
        }
    }
    
    
    /**
     * Converts '/' to '\' for windows, '\' to '/' for 'nix.
     * 
     * @param string $filename the filename to unify
     * 
     * @return string the filename with appropriate slashes
     * 
     */
    public function unifySlashes($filename)
    {
        if ($this->checkCurrentOS('Win')) { // replace '/' with '\'
            return str_replace('/', '\\', $filename); 
        } else { // for 'nix, assume '\' is a directory separator
            return str_replace('\\', '/', $filename); 
        }
    }
 
    
    /**
     * Checks if the currect operating system matches.  
     * Choices for $operatingSystem are "Win" and "_Nix".
     * 
     * @param string $operatingSystem the operating system to check against
     * 
     * @return bool true if the same, false if not
     * 
     * @author <ilgarm@azeronline.com>
     */
    public function checkCurrentOS($operatingSystem)
    {
        if (strcmp($operatingSystem, $this->currentOS) == 0) {
            return true;
        } else {
            return false;
        }
    }


    /**
     * Returns if the current operating system is Windows.
     * 
     * @return boolean If the current system is a windows OS.
     */
    public function isWindows()
    {
        return $this->checkCurrentOS("Win");
    }


    /**
     * Returns if the current operating system is Linux or Unix.
     *
     * @return boolean
     */
    public function isNix()
    {
        return $this->checkCurrentOS("_Nix");
    }
    
    
    /**
     * Checks if the path passed in is relative or not.
     * 
     * @param string $dir the directory string to check
     * 
     * @return bool true if relative, false otherwise
     * 
     * @author <ilgarm@azeronline.com>
     * 
     */
    protected function isRelative($dir)
    {
        if ($dir[0] == '\\') { // windows style absolute
            return true;
        } else if ($this->checkCurrentOS("Win")) {
            return (preg_match("/^\w+:/", $dir) <= 0);
        } else {
            return (preg_match("/^\//", $dir) <= 0);
        }
    }
    
    /**
     * The currently running operating system.
     * 
     * @var string
     */
    protected $currentOS = null;
    
}
?>
